(function ($) {

	if (typeof $.esmool == 'undefined')
		$.esmool = {};

	var beginShow = () => {};
	var endShow = () => {};
	var showFits = () => {};

	$.esmool.nnInit = (beginShowFunc, showFitsFunc, endShowFunc) => {
		beginShow = beginShowFunc;
		showFits = showFitsFunc;
		endShow = endShowFunc;
	};

	/**
	 * 构造前向神经网络
	 * @param layout[] layouts 神经网络对象
	 *        { weights, bias, activate, inputCount } layout 层配置函数
	 *        double[][] weights 输入该层的权重矩阵
	 *        double[] bias 该层的偏置向量
	 *        double -> double activate 该层的激励函数
	 *        int nInput 该层上级输入数
	 * @return double[] -> double[] 函数, 构造的神经网络函数
	 */
	$.esmool.nnBuild = layouts => {
		var nLayout = layouts.length;

		return input => {
			var data = input;
			var k = 0;
			while (k < nLayout) {
				var layout = layouts[k++];

				var w = layout.weights;		// 输入第 k 层的权重
				var b = layout.bias;		// 输入第 k 层的偏置
				var act = layout.activate;	// 第 k 层的输出激励
				var u = vecAdd(matTimes(w, data), b);

				data = vecActivate(u, act);
			}

			return data;
		};

	};

	/**
	 * 初始化一个随机的前向神经网络
	 * @param layoutConfig[] layoutNodes 网络配置对象
	 *        { count, activateType } layoutConfig 层配置对象
	 *        int count 层内节点数
	 *        [ 'idx' | 'signod' ] activateType 激励函数类型
	 * @return layouts 神经网络对象
	 */
	$.esmool.nnConfig = layoutConfigs => {
		var rand = () => Math.random() * 2 - 1;
		var nLayout = layoutConfigs.length;

		var layouts = new Array(nLayout);

		var k = 0;
		var nInput = 0;
		while (k < nLayout) {
			var config = layoutConfigs[k];
			var count = config.count;

			nInput = k == 0 ? count : nInput;
			var weights = k == 0 ? matEye(count) : new Array(count);
			var bias = k == 0 ? vecZeros(nInput) : new Array(count);

			for (var i=0; k>0 && i<count; i++) {
				weights[i] = new Array(nInput);
				bias[i] = rand();
				for (var j=0; j<nInput; j++)
					weights[i][j] = rand();
			}

			var activate = actiavteFuncs[config.activateType];

			layouts[k++] = {
				weights: weights,
				bias: bias,
				activate: activate,
				nInput: nInput
			};

			nInput = count;
		}

		return layouts;
	};

	/**
	 * 变异函数
	 * @param  layout[] layouts 神经网络对象
	 * @param  double rate 变异率
	 * @param  double -> double mutator 变异函数
	 * @return layout[] layouts 变异后的网络对象
	 */
	$.esmool.nnMutate = (layouts, rate , mutator) => {
		var tLayouts = JSON.stringify(layouts);
		tLayouts = JSON.parse(tLayouts);
		for (var i=0; i<tLayouts.length; i++)
			tLayouts[i].activate = layouts[i].activate;

		var rand = Math.random;
		if (rand() > rate)
			return tLayouts;

		var nLayout = tLayouts.length - 1; // 不要动输入层
		var mLayout = Math.floor(nLayout * rand()) + 1;
		var layout = tLayouts[mLayout];

		var nOutput = layout.bias.length;
		var mOutput = Math.floor(nOutput * rand());

		var nInput = layout.nInput;
		var dies = rand() * (nInput + 1);
		if (dies < 1) { // mutate bias
			layout.bias[mOutput] = mutator(layout.bias[mOutput]);
		} else { // mutate weights
			mInput = Math.floor(nInput * rand());
			layout.weights[mOutput][mInput] = mutator(layout.weights[mOutput][mInput]);
		}

		return tLayouts;
	};

	/**
	 * 神经网络对象接合
	 * @param  layout[] fathor 父网络
	 * @param  layout[] mother 母网络
	 * @return layout[] 子网络
	 */
	$.esmool.nnCombine = (fathor, mother) => {
		// NO NEED THIS: 不一定要进行有性生殖, 这里没办法设计一个合适的方式始子代兼具父母的优良特性, 所以就直接进行无性生殖好了
	};

	var actiavteFuncs = {
		'id'		: x => x,
		'step'		: x => x >= 0 ? 1 : 0,
		'sigmoid'	: x => 1 / (1 + Math.exp(-x)),
		'tanh'		: x => Math.tanh(x),
		'atan'		: x => Math.atan(x),
		'softsign'	: x => x / (1 + Math.abs(x))
	};

	/**
	 * 向量式激活函数
	 * @param  double[] u 输入
	 * @param  double -> double activate 激活函数
	 * @return double[] -> double[] 针对向量各个分量分别应用的激活函数
	 */
	var vecActivate = (u, activate) => {
		var y = new Array(u.length);
		for (var i=0; i<u.length; i++)
			y[i] = activate(u[i]);
		return y;
	};

	/**
	 * 矩阵乘法
	 * @param  double[][] m
	 * @param  double[] a
	 * @return double[] c = m * a
	 */
	var matTimes = (m, a) => {
		var n = m.length;
		var r = m[0].length;
		if (r != a.length)
			throw new Error('矩阵维数不匹配不能相乘');

		var c = new Array(n);
		for (var i=0; i<n; i++) {
			c[i] = 0;
			for (var j=0; j<r; j++)
				c[i] += m[i][j] * a[j];
		}
		return c;
	};

	/**
	 * 向量加法
	 * @param  double[] a
	 * @param  double[] b
	 * @return double[] c = a + b
	 */
	var vecAdd = (a, b) => {
		var len = a.length;
		if (len != b.length)
			throw new Error('两个向量维数不同不能相加');

		var c = new Array(len);
		for (var i=0; i<len; i++)
			c[i] = a[i] + b[i];

		return c;
	};

	var matEye = n => {
		var m = new Array(n);
		for (var i=0; i<n; i++) {
			m[i] = new Array(n);
			for (var j=0; j<n; j++)
				m[i][j] = i == j ? 1 : 0;
		}
		return m;
	};

	var vecZeros = n => {
		var v = new Array(n);
		for (var i=0; i<n; i++)
			v[i] = 0;
		return v;
	};

})(jQuery);